/* Text file routines */

#include "global.h"
#include "text.h"			/* text resource file ids */
#include "vu-res.h"		/* general resource file ids */

#define TEXT_PERCENTAGE					75
#define MAX_TEXT_LINE_LENGTH			256
#define SEARCH_LIMIT						80
#define CS_NO								1
#define CS_YES								2

/* Text file data */
typedef struct {
	INT		lines ;				/* number of lines on screen */
	LONG		curr_pos ;			/* an offset position from start of file in bytes */
	LONG		curr_line ;			/* current line */
	LONG		num_lines ;			/* number of lines */
	LONG		last_top_line ;	/* last top line */
	TEXT		searchstr[SEARCH_LIMIT + 1] ;		/* BCS search string */
	UWORD		cs ;					/* Case sensitive? */
	BOOL		redraw ;
	INT		file_op ;
	TEXT **	buffers ;
} TXT_DATA ;

LOCAL_D TXT_DATA *		pdata = NULL ;

LOCAL_D H_MENU_DATA			Textmdata[] = {	"File", 3,
															"Text", 3,
															NULL } ;

LOCAL_D TEXT *					Textcmds[] = {	"oOpen",
														"sSave",
														"xExit",

														"fFind text",
														"gFind again",
														"jJump to line",
														NULL } ;

LOCAL_C LONG GetLineNum(VOID) {
	LONG					tlong ;
	H_DI_NUMBER			num ;

	tlong = pdata->curr_line ;
	num.value = &tlong ;
	num.high = pdata->last_top_line ;
	num.low = 1L ;

	Res_OpenDialog(RS_TEXT_GOTO_LINE) ;
	Res_AddDialogItem(H_DIALOG_NUMBER, RS_TEXT_LINE, &num) ;
	if ( !Res_RunDialog() )
		return(0L) ;
	else
		return(tlong) ;
}

LOCAL_C VOID Rewind(VOID) {
	f_seek(file.pfcb, P_FREWIND, NULL) ;
	pdata->curr_pos = 0L ;
	pdata->curr_line = 1L ;
}

LOCAL_C VOID MoveLinesRel(LONG lines_rel) {
	TEXT			str[MAX_TEXT_LINE_LENGTH] ;
	LONG			target_line ;

	f_seek(file.pfcb, P_FRSET, &pdata->curr_pos) ;

	if ( lines_rel == 0 )
		return ;

	/* Calculate the line to be found */
	target_line = lines_rel + pdata->curr_line ;

	if ( target_line <= 1L )
		target_line = 1L ;
	else if ( target_line >= pdata->last_top_line )
		target_line = pdata->last_top_line ;

	/* Rewind to beginning */
	if ( lines_rel < 0 )
		Rewind() ;

	/* Move Forwards */
	/* Read Current Line */
	f_read(file.pfcb, str, MAX_TEXT_LINE_LENGTH - 1) ;
	for (	; pdata->curr_line < target_line ; pdata->curr_line++ )
		f_read(file.pfcb, str, MAX_TEXT_LINE_LENGTH - 1) ;

	f_seek(file.pfcb, P_FRSENSE, &pdata->curr_pos) ;
}

LOCAL_C VOID Find(BOOL again) {
	LONG				store_line ;
	LONG				store_pos ;
	LONG				tlong ;
	H_DI_NUMBER		num ;
	H_DI_SEDIT		sedit = { NULL, SEARCH_LIMIT, 25 } ;
	TEXT				buf[MAX_TEXT_LINE_LENGTH] ;
	TEXT				str[SEARCH_LIMIT + 3] = "" ; /* ZTS search string */
	BOOL				found = FALSE ;

	f_seek(file.pfcb, P_FRSET, &pdata->curr_pos) ;

	tlong = pdata->curr_line ;
	if ( !again ) {
		num.value = &tlong ;
		num.high = pdata->last_top_line ;
		num.low = 1L ;

		sedit.str = pdata->searchstr ;

		Res_OpenDialog(RS_TEXT_FIND) ;
		Res_AddDialogItem(H_DIALOG_SEDIT, RS_TEXT_FIND, &sedit) ;
		Res_AddDialogItem(H_DIALOG_NUMBER, RS_TEXT_FROM_LINE, &num) ;
		Res_AddChoiceList(RS_TEXT_CASE_SENSE, RS_NO, RS_YES, &pdata->cs) ;
		if ( !Res_RunDialog() )
			return ;
	}

	/* Move to the required starting line */
	MoveLinesRel(tlong - pdata->curr_line) ;

	/* Add wildcards to beginning and end */
	p_scatm(str, "*", BCStoZTS(pdata->searchstr), "*", NULL) ;
	ZTStoBCS(pdata->searchstr) ;

	store_pos = pdata->curr_pos ;
	store_line = pdata->curr_line ;

	/* Search for string, reading and ignoring first line */
	if ( pdata->curr_line < pdata->num_lines )
		f_read(file.pfcb, buf, MAX_TEXT_LINE_LENGTH - 1) ;
	for ( ; pdata->curr_line < pdata->num_lines ; ) {
		buf[f_read(file.pfcb, buf, MAX_TEXT_LINE_LENGTH - 1)] = '\x0' ;
		pdata->curr_line++ ;
		if ( pdata->cs == CS_YES )
			found = p_smatch(buf, str) ;
		else
			found = p_smatchi(buf, str) ;
		if ( found ) {
			/* Found */
			f_seek(file.pfcb, P_FRSENSE, &pdata->curr_pos) ;
			if ( pdata->curr_line > pdata->last_top_line )
				MoveLinesRel(pdata->last_top_line - pdata->curr_line) ;
			return ;
		}
	}

	/* If not found */
	InfoMsg(RS_TEXT_NOT_FOUND) ;
	pdata->curr_pos = store_pos ;
	pdata->curr_line = store_line ;
	f_seek(file.pfcb, P_FRSET, &pdata->curr_pos) ;
}

LOCAL_C VOID LoadText(VOID) {
	INT		max_width = 0 ;
	INT		iLp ;
	INT		lines_to_read ;
	INT		line_length = 0 ;

	if ( pdata->file_op == FO_NONE )
		return ;

	switch (pdata->file_op) {
		case FO_GOTO :
			MoveLinesRel(GetLineNum() - pdata->curr_line) ;
			break ;

		case FO_FIND :
			Find(FALSE) ;
			break ;

		case FO_AGAIN :
			Find(TRUE) ;
			break ;

		case FO_TOP :
			Rewind() ;
			break ;
			
		case FO_BOTTOM :
			MoveLinesRel(pdata->last_top_line - pdata->curr_line) ;
			break ;

		case FO_UP :
			MoveLinesRel(-1) ;
			break ;

		case FO_DOWN :
			MoveLinesRel(1) ;
			break ;

		case FO_PAGE_UP :
			MoveLinesRel(-pdata->lines) ;
			break ;

		case FO_PAGE_DOWN :
			MoveLinesRel(pdata->lines) ;
			break ;

		default :
			return ;
	}

	lines_to_read = MIN(pdata->lines, (INT) pdata->num_lines) ;

	f_seek(file.pfcb, P_FRSET, &pdata->curr_pos) ;

	/* Read text into buffer */
	for (	iLp = 0 ; iLp < lines_to_read ; iLp++ ) {
		line_length = f_read(file.pfcb, pdata->buffers[iLp], MAX_TEXT_LINE_LENGTH - 1) ;
		pdata->buffers[iLp][line_length] = '\x0' ;
		max_width = MAX(max_width, gTextWidth(	WS_FONT_SYSTEM,
															G_STY_NORMAL,
															pdata->buffers[iLp],
															line_length ) ) ;
	}

	SetMainWindowExtent(0, 0, max_width, lines_to_read * (metrics.text_height + TEXT_LEADING)) ;

	/* Draw lines on screen */
	for ( iLp = 0 ; iLp < lines_to_read ; iLp++ )
		PrintString(pdata->buffers[iLp], iLp) ;

	f_seek(file.pfcb, P_FRSET, &pdata->curr_pos) ;
}

#pragma save, ENTER_CALL

LOCAL_C INT TextCleanUp(VOID) {
	if ( file.pfcb != NULL ) {
		p_close(file.pfcb) ;
		file.pfcb = NULL ;
	}

	if ( pdata != NULL ) {
		if ( pdata->buffers != NULL ) {
			INT		iLp ;

			for ( iLp = 0 ; iLp < pdata->lines ; iLp++ ) {
				p_free(pdata->buffers[iLp]) ;
				pdata->buffers[iLp] = NULL ;
			}

			p_free(pdata->buffers) ;
			pdata->buffers = NULL ;
		}

		p_free(pdata) ;
		pdata = NULL ;
	}

	return(E_APP_NONE) ;
}

LOCAL_C INT TextInit(VOID) {
	INT		iLp ;
	TEXT		str[MAX_TEXT_LINE_LENGTH] ;

	pdata = f_alloc(sizeof(TXT_DATA)) ;

	pdata->buffers = NULL ;
	pdata->lines = metrics.disp_size.y / (metrics.text_height + TEXT_LEADING) ;
	pdata->buffers = f_alloc(pdata->lines * sizeof(TEXT *)) ;

	pdata->curr_pos = 0L ;
	pdata->curr_line = 1 ;
	pdata->searchstr[0] = '\x0' ;
	pdata->cs = CS_NO ;
	pdata->redraw = FALSE ;
	pdata->file_op = FO_TOP ;

	for ( iLp = 0 ; iLp < pdata->lines ; pdata->buffers[iLp++] = NULL ) ;
	/* Allocate memory for text buffer */
	for ( iLp = 0 ; iLp < pdata->lines ; iLp++ )
		pdata->buffers[iLp] = f_alloc(MAX_TEXT_LINE_LENGTH) ;

	f_open(&file.pfcb, file.name, P_FTEXT | P_FRANDOM | P_FSHARE) ;

	for (	pdata->num_lines = 0 ;
			p_read(file.pfcb, str, MAX_TEXT_LINE_LENGTH - 1) >= 0 ;
			pdata->num_lines++ ) ;

	pdata->last_top_line = pdata->num_lines < pdata->lines ? 1 : pdata->num_lines - pdata->lines + 1 ;

	f_seek(file.pfcb, P_FRSET, &pdata->curr_pos) ;
	return(E_APP_NONE) ;
}

LOCAL_C INT TextUpdate(VOID) {
	TEXT						buf[128] ;
	P_EXTENT					extent ;
	SCROLL_BAR_STATE		barstate ;

	if (	pdata->redraw ||
			pdata->file_op != FO_NONE ) {

		if ( pdata->file_op != FO_NONE ) {
			LoadText() ;
			pdata->file_op = FO_NONE ;
		}

		/* Filename : xx/xx */
		p_atos(	buf, "%s : %lu/%lu",
					file.name, pdata->curr_line, pdata->num_lines ) ;
		UpdateStatus(buf) ;

		GetMainWindowExtent(&extent) ;

		barstate.start = pdata->curr_line - 1 ;
		barstate.size = pdata->lines ;
		barstate.total = pdata->num_lines - 1 ;
		UpdateYScroll(&barstate) ;

		barstate.start = -extent.tl.x ;
		barstate.size = metrics.disp_size.x ;
		barstate.total = extent.width ;
		UpdateXScroll(&barstate) ;

		pdata->redraw = FALSE ;
	}
	return(FALSE) ;
}

LOCAL_C INT TextFileInfo(VOID) {
	TEXT *		str = NULL ;
	H_DI_TEXT	text = { NULL, H_DTEXT_ALIGN_LEFT } ;
	INT			ret ;

	/* Display special info for text files:
		Lines: xx
	*/
	ReadResource(RS_TEXT_LINES, &str) ;
	text.str = p_alloc(p_slen(str) + 20) ;

	if ( text.str == NULL ) {
		p_free(str) ;
		return(E_APP_MEM) ;
	}

	p_atos(&text.str[1], str, pdata->num_lines) ;
	p_free(str) ;
	text.str[0] = p_slen(&text.str[1]) ;
	ret = uAddDialogItem(H_DIALOG_TEXT, NULL, &text) ;
	p_free(text.str) ;
	return(ret) ;
}

LOCAL_C INT TextMenuExec(INT index) {
	switch ( index ) {
		case 0 :
			MainDialog() ;
			break ;
		case 1 :
			/* SaveText() ; */
			break ;
		case 2 :
			Quit() ;
			break ;
		case 3 :
			pdata->file_op = FO_FIND ;
			break ;
		case 4 :
			pdata->file_op = FO_AGAIN ;
			break ;
		case 5 :
			pdata->file_op = FO_GOTO ;
			break ;
		default :
			return(E_APP_NONE) ;
	}

	return(E_APP_NONE) ;
}

LOCAL_C INT TextPlainKey(WMSG_KEY *pkey) {
	switch (pkey->keycode) {
		case W_KEY_LEFT :
		case W_KEY_RIGHT :
		case W_KEY_HOME :
		case W_KEY_END :
			pdata->redraw = MoveMainWindow(pkey->keycode) ;
			break ;
		case W_KEY_DOWN :
			if ( pdata->curr_line < pdata->last_top_line )
				pdata->file_op = pkey->modifiers & W_CTRL_MODIFIER ? FO_BOTTOM : FO_DOWN ;
			break ;
		case W_KEY_UP :
			if ( pdata->curr_line > 1 )
				pdata->file_op = pkey->modifiers & W_CTRL_MODIFIER ? FO_TOP : FO_UP ;
			break ;
		case W_KEY_PAGE_DOWN :
			pdata->file_op = pdata->curr_line < pdata->last_top_line ? FO_PAGE_DOWN : FO_NONE ;
			break ;
		case W_KEY_PAGE_UP :
			pdata->file_op = pdata->curr_line > 1 ? FO_PAGE_UP : FO_NONE ;
			break ;
		case 'g' :
		case 'j' :
			/* Goto specified line */
			pdata->file_op = FO_GOTO ;
			break ;
		case 'n' :
			/* Repeat last search */
			pdata->file_op = pdata->curr_line < pdata->last_top_line ? FO_AGAIN : FO_NONE ;
			break ;
		case 'f' :
		case 's' :
		case '.' :
			/* Case-insensitive search */
			pdata->file_op = FO_FIND ;
			pdata->cs = CS_NO ;
			break ;
		case 'S' :
		case '/' :
			/* Case-sensitive search */
			pdata->file_op = FO_FIND ;
			pdata->cs = CS_YES ;
			break ;
		default :
			break ;
	}
	return(E_APP_NONE) ;
}

#pragma restore

LOCAL_C INT TextRecog(BYTE *buf, UINT buflen) {
	UINT		count ;
	UINT		iLp ;

	for ( count = 0, iLp = 0 ; iLp < buflen ; iLp++ )
		count += (	buf[iLp] == '\r' ||
						buf[iLp] == '\n' ||
						p_isprint(buf[iLp]) ) ;

	return( count > (buflen * TEXT_PERCENTAGE) / 100 ) ;
}

GLDEF_C VOID TextDefns(DEFNS *pdefns) {
	pdefns->pfRecognize =	TextRecog ;

	pdefns->pcmds =			Textcmds ;
	pdefns->pmdata =			Textmdata ;
	pdefns->nHelp =			RH_TEXT ;
	pdefns->flags =			STATUS_BAR | SCROLL_Y | SCROLL_X | SUPER ;
	pdefns->style =			G_STY_NORMAL ;

	pdefns->pfCleanUp =		TextCleanUp ;
	pdefns->pfInit =			TextInit ;
	pdefns->pfFileInfo =		TextFileInfo ;
	pdefns->pfUpdate =		TextUpdate ;
	pdefns->pfMenuExec =		TextMenuExec ;
	pdefns->pfPlainKey =		TextPlainKey ;
}

